package educrypt.ciphers;

import java.util.ArrayList;
import java.util.List;

import educrypt.commons.Alphabet;
import educrypt.commons.Text;
import educrypt.i18n.Messages;
import educrypt.log.ObservableLogger;
import educrypt.log.SimpleLogger;

public class Vigenere {

	private Alphabet alphabet;
	private Alphabet[] keys;

	public static String formatKeyword(String keyword, Alphabet alphabet) {
		return alphabet.filterKnownSymbols(keyword.toLowerCase());
	}

	public Vigenere(Alphabet alphabet, String keyword) {
		this.alphabet = alphabet;
		List<Integer> offsets = this.keywordToNumericOffsets(keyword);
		this.generateKeys(offsets);

		this.logCipherDetails(keyword, offsets);
	}

	private void logCipherDetails(String keyword, List<Integer> offsets) {
		// Log to console
		SimpleLogger logger = ObservableLogger.getInstance();
		logger.log(SimpleLogger.TITLE,
				"---- " + Messages.getString("cipherVigenere") + " ----");

		logger.log(SimpleLogger.TITLE, Messages.getString("guiAlphabet"));
		logger.log(SimpleLogger.CONTENT, alphabet.getSymbols());

		logger.log(SimpleLogger.TITLE, Messages.getString("guiKeyword"));
		logger.log(SimpleLogger.CONTENT, keyword + "  ->  " + offsets);

		logger.log(SimpleLogger.TITLE, Messages.getString("guiGeneratedKey"));
		for (Alphabet key : keys) {
			logger.log(SimpleLogger.CONTENT, key.getSymbols());
		}
	}

	public Vigenere(Alphabet alphabet, List<Integer> offsets) {
		this.alphabet = alphabet;
		this.generateKeys(offsets);
		// This constructor is used when the cipher is broken, no need to log it
	}

	private List<Integer> keywordToNumericOffsets(String keyword) {
		List<Integer> offsets = new ArrayList<Integer>(keyword.length());
		for (int i = 0; i < keyword.length(); i++) {
			int offset = this.alphabet.getSymbolPosition(keyword.charAt(i));
			offsets.add(offset);
		}
		return offsets;
	}

	private void generateKeys(List<Integer> offsets) {
		this.keys = new Alphabet[offsets.size()];
		for (int i = 0; i < offsets.size(); i++) {
			keys[i] = this.alphabet.rotate(-offsets.get(i));
		}
	}

	public Text encode(Text plainText) {
		SimpleLogger logger = ObservableLogger.getInstance();

		StringBuffer cipherText = new StringBuffer(plainText.size());
		int realPosition = 0; // position not counting white spaces and unknown
								// symbols
		for (char symbol : plainText) {
			// We ignore white spaces and unknown symbols
			if (Character.isWhitespace(symbol)
					|| !alphabet.containsSymbol(symbol)) {
				cipherText.append(symbol);
			} else {
				cipherText.append(encodeSymbol(symbol, realPosition
						% keys.length));
				realPosition++;
			}
		}

		logger.log(SimpleLogger.SUCCESS,
				Messages.getString("guiSuccesfulEncoding"));
		return new Text(cipherText.toString());
	}

	private char encodeSymbol(char symbol, int position) {

		boolean isUpperCase = Character.isUpperCase(symbol);

		int pos = alphabet.getSymbolPosition(symbol);

		char cipherSymbol = keys[position].getSymbolAt(pos);

		if (isUpperCase) {
			cipherSymbol = Character.toUpperCase(cipherSymbol);
		}

		return cipherSymbol;
	}

	public Text decode(Text cipherText) {
		StringBuffer plainText = new StringBuffer(cipherText.size());
		int realPosition = 0; // position not counting white spaces
		for (char symbol : cipherText) {
			if (Character.isWhitespace(symbol)
					|| !alphabet.containsSymbol(symbol)) {
				plainText.append(symbol);
			} else {
				plainText.append(decodeSymbol(symbol, realPosition
						% keys.length));
				realPosition++;
			}
		}
		SimpleLogger logger = ObservableLogger.getInstance();
		logger.log(SimpleLogger.SUCCESS,
				Messages.getString("guiSuccesfulDecoding"));
		return new Text(plainText.toString());
	}

	private char decodeSymbol(char symbol, int position) {
		assert alphabet.containsSymbol(symbol);

		boolean isUpperCase = Character.isUpperCase(symbol);

		int pos = keys[position].getSymbolPosition(symbol);

		char plainSymbol = alphabet.getSymbolAt(pos);

		if (isUpperCase) {
			plainSymbol = Character.toUpperCase(plainSymbol);
		}
		return plainSymbol;
	}
}
